package es.cv.core;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

import org.opencv.core.*;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.features2d.Features2d;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgproc.Moments;
import org.opencv.objdetect.CascadeClassifier;

//import com.sun.javafx.geom.AreaOp.AddOp;

public class CoreModule {

	public static BufferedImage matToBufferedImage(Mat matrix, String imageEncodingFormat) {
		MatOfByte matOfByte = new MatOfByte();
		Highgui.imencode(imageEncodingFormat, matrix, matOfByte);
		byte[] byteArray = matOfByte.toArray();
		try {
			return ImageIO.read(new ByteArrayInputStream(byteArray));
		} catch (IOException e) {
			System.out.println(e.getMessage());
			return null;
		}
	}

	public static Mat readMatFromImageFile(String filename) {
		return Highgui.imread(filename);
	}

	public static void writeMatToFile(Mat matrix, String filename) {
		Highgui.imwrite(filename, matrix);
	}

	public static void matToGraColor(Mat original, Mat gray) {
		Imgproc.cvtColor(original, gray, Imgproc.COLOR_RGB2GRAY);
	}
	
	public static void matToColor(Mat original, Mat color) {
		Imgproc.cvtColor(original, color, Imgproc.COLOR_GRAY2RGB);
	}

	public static void matToHSVColor(Mat original, Mat hsv) throws Exception {
		try {
			Imgproc.cvtColor(original, hsv, Imgproc.COLOR_RGB2HSV);
		} catch (Exception e) {
			throw new Exception("Excepcion al convertir a HSV");
		}
	}

	public static void matToCany(Mat original, Mat imageCany) {
		Imgproc.Canny(original, imageCany, 50, 200, 3, true);
	}

	public static void resizeMat(Mat original, Mat resizeimage, Size size) {
		Imgproc.resize(original, resizeimage, size);
	}

	public static void equilizer(Mat original, Mat equalizer) {
		Imgproc.equalizeHist(original, equalizer);
	}

	public static void backProjection(Mat source, Mat backProj) {
		Mat original = new Mat();

		// Suavizado
		gaussian(source, original);

		// HSV
		Mat hsv = new Mat();
		try {
			matToHSVColor(original, hsv);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// Histograma
		MatOfInt mChannels[] = new MatOfInt[] { new MatOfInt(0), new MatOfInt(1), new MatOfInt(2) };
		Mat mask = new Mat();
		Mat hist = new Mat();
		MatOfInt histSize = new MatOfInt(25);
		MatOfFloat ranges = new MatOfFloat(0f, 256f);
		Imgproc.calcHist(Arrays.asList(hsv), mChannels[0], mask, hist, histSize, ranges);

		// Normalizar histograma
		Size sizeRgba = original.size();
		Core.normalize(hist, hist, sizeRgba.height / 2, 0, Core.NORM_MINMAX);

		// BackProjection
		Imgproc.calcBackProject(Arrays.asList(hsv), mChannels[0], hist, backProj, ranges, 1);
	}

	/*
	 * threshold_type 0: Binary 1: Binary Inverted 2: Threshold Truncated 3:
	 * Threshold to Zero 4: Threshold to Zero Inverted
	 */
	public static void threshold(Mat src, int threshold_value, int max_BINARY_value, int threshold_type, Mat imageMod) {
		Imgproc.cvtColor(src, imageMod, Imgproc.COLOR_RGB2GRAY);
		Imgproc.threshold(imageMod, imageMod, threshold_value, max_BINARY_value, threshold_type);
	}

	public static Mat watershed(Mat src, boolean watershed) {
		// Get the binary map
		Mat binary = new Mat();
		Imgproc.cvtColor(src, binary, Imgproc.COLOR_BGR2GRAY);
		Imgproc.threshold(binary, binary, 100, 255, Imgproc.THRESH_BINARY);

		// Eliminate noise and smaller objects
		Mat fg = new Mat();
		Imgproc.erode(binary, fg, new Mat(), new Point(-1, -1), 6);

		// Identify image pixels without objects
		Mat bg = new Mat();
		Imgproc.dilate(binary, bg, new Mat(), new Point(-1, -1), 6);
		Imgproc.threshold(bg, bg, 1, 128, Imgproc.THRESH_BINARY_INV);

		// Show markers image
		Mat markers = new Mat(binary.size(), CvType.CV_8U, new Scalar(0));
		Core.add(fg, bg, markers);

		// Create watershed segmentation object
		WatershedSegmenter segmenter = new WatershedSegmenter();
		// Set markers and process
		segmenter.setMarkers(markers);
		segmenter.process(src);

		if (watershed) {
			return segmenter.getWaterSheds();
		} else {
			return segmenter.getSegmentation();
		}
	}
	
	public static Mat findContours(Mat src){
		Mat gray = new Mat();
		Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
		Imgproc.Canny(gray, gray, 100, 200, 3, false);
		//find contours
		List<MatOfPoint> contours = new Vector<MatOfPoint>();
		Mat hierarchy = new Mat();
		Random rnd = new Random(System.currentTimeMillis());
		Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0,0));
		Mat drawing = new Mat();
		drawing = Mat.zeros(gray.size(), CvType.CV_8UC3);
		for (int i = 0; i < contours.size(); i++) {
			Scalar color = new Scalar(rnd.nextInt(256),rnd.nextInt(256),rnd.nextInt(256));
			Imgproc.drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, new Point());
		}
		return drawing;
	}
	
	public static Mat sift(Mat src){
		Mat gray = new Mat();
		Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
		MatOfKeyPoint keypoints = new MatOfKeyPoint();
		FeatureDetector featureDetector = FeatureDetector.create(3); // SIFT=3
		featureDetector.detect(gray, keypoints);
		//System.out.println("Detected "+keypoints.size()+" keypoints");
		Mat result = new Mat();
		Features2d.drawKeypoints(gray, keypoints, result);		
		return result;
	}
	
	public static void sift(Mat src1, Mat src2, Mat dst_img_matches){
		Mat gray1 = new Mat();
		Mat gray2 = new Mat();
		Imgproc.cvtColor(src1, gray1, Imgproc.COLOR_BGR2GRAY);
		Imgproc.cvtColor(src2, gray2, Imgproc.COLOR_BGR2GRAY);
		MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
		MatOfKeyPoint keypoints2 = new MatOfKeyPoint();

		// construct a SIFT object.
		FeatureDetector featureDetector = FeatureDetector.create(3); // SIFT=3
		
		// detect function finds the keypoint in the images
		// Each keypoint is a special structure which has many attributes like its (x,y) coordinates,
		// size of the meaningful neighbourhood, angle which specifies its orientation, 
		// response that specifies strength of keypoints etc.
		featureDetector.detect(gray1, keypoints1);
		featureDetector.detect(gray2, keypoints2);

		//System.out.println("Detected "+keypoints.size()+" keypoints");
		Mat result1 = new Mat();
		Mat result2 = new Mat();
		// drawKeypoints function draws the small circles on the locations of keypoints. 
		Features2d.drawKeypoints(gray1, keypoints1, result1);
		Features2d.drawKeypoints(gray2, keypoints2, result2);

		//CoreModule.writeMatToFile(result1, "carta_sift.jpg");
		//CoreModule.writeMatToFile(result2, "carta_escalada_sift.jpg");

		//Features2d.drawKeypoints(gray, keypoints1, result, new Scalar(new Random().nextInt(256),new Random().nextInt(256),new Random().nextInt(256)), Features2d.DRAW_RICH_KEYPOINTS);
		
		DescriptorExtractor descriptorExtractor = DescriptorExtractor.create(1);
		Mat descriptors1 = new Mat();
		Mat descriptors2 = new Mat();
				
		descriptorExtractor.compute(gray1, keypoints1, descriptors1);
		descriptorExtractor.compute(gray2, keypoints2, descriptors2);

		//Mat img_matches = new Mat();
	    DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);
		MatOfDMatch matches = new MatOfDMatch();
		matcher.match(descriptors1, descriptors2, matches);
		Features2d.drawMatches(gray1, keypoints1, gray2, keypoints2, matches, dst_img_matches);
		
		//CoreModule.writeMatToFile(dst_img_matches, "img_matches.jpg");	
	}
	
	public static Mat surf(Mat src){
		Mat gray = new Mat();
		Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
		MatOfKeyPoint keypoints = new MatOfKeyPoint();
		FeatureDetector featureDetector = FeatureDetector.create(FeatureDetector.SURF); // SURF
		featureDetector.detect(gray, keypoints);
		//System.out.println("Detected "+keypoints.size()+" keypoints");
		Mat result = new Mat();
		Features2d.drawKeypoints(gray, keypoints, result);		
		return result;
	}

	public static void gaussian(Mat original, Mat gaussian) {
		Imgproc.GaussianBlur(original, gaussian, new Size(5, 5), 0, Imgproc.BORDER_DEFAULT);
	}

	public static void convolucion(Mat source, Mat kernel, Mat destination) {
		Imgproc.filter2D(source, destination, -1, kernel);
	}

	public static void eroding(Mat image, int erosion_elem, int kernelSize, Mat imageMod) {
		if (erosion_elem == 0) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
		} else if (erosion_elem == 1) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
		} else if (erosion_elem == 2) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
		}
	}

	public static void dilating(Mat image, int dilation_elem, int kernelSize, Mat imageMod) {
		if (dilation_elem == 0) {
			Imgproc.dilate(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
		} else if (dilation_elem == 1) {
			Imgproc.dilate(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
		} else if (dilation_elem == 2) {
			Imgproc.dilate(image, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
		}
	}

	public static void opening(Mat image, int element, int kernelSize, Mat imageMod) {
		if (element == 0) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
			Imgproc.dilate(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
		} else if (element == 1) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
			Imgproc.dilate(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
		} else if (element == 2) {
			Imgproc.erode(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
			Imgproc.dilate(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
		}
	}

	public static void closing(Mat image, int element, int kernelSize, Mat imageMod) {
		if (element == 0) {
			Imgproc.dilate(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
			Imgproc.erode(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize)));
		} else if (element == 1) {
			Imgproc.dilate(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
			Imgproc.erode(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(kernelSize, kernelSize)));
		} else if (element == 2) {
			Imgproc.dilate(image, imageMod, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
			Imgproc.erode(imageMod, imageMod,
					Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize)));
		}
	}

	public static void huMoments(Mat image, Mat hu){
		Moments mom = new Moments();
		mom = Imgproc.moments(image, true);
		//Hu moments
		Imgproc.HuMoments(mom, hu);
	}
	
	public static double getArea(Mat image){
		Moments mom = new Moments();
		mom = Imgproc.moments(image, false);
		return mom.get_m00();
	}
	
	public static double getPerimeter(Mat image){
	    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();    
	    Imgproc.findContours(image, contours, new Mat(), Imgproc.RETR_LIST,Imgproc.CHAIN_APPROX_SIMPLE);
		MatOfPoint2f pointsf = new MatOfPoint2f();
		contours.get(0).convertTo(pointsf, CvType.CV_32F);
	    return Imgproc.arcLength(pointsf, true);
	}
	
	public static RotatedRect getAxes(Mat image){
	    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();    
	    Imgproc.threshold(image, image, 70, 255, Imgproc.THRESH_BINARY);
	    Imgproc.findContours(image, contours, new Mat(), Imgproc.RETR_LIST,Imgproc.CHAIN_APPROX_SIMPLE);
		MatOfPoint2f pointsf = new MatOfPoint2f();
		contours.get(0).convertTo(pointsf, CvType.CV_32F);
	    return Imgproc.fitEllipse(pointsf);
	}
	
	public static void bordesConv(Mat image, Mat imageMod) throws Exception {
		try {
			Mat rectas = new Mat();
			Mat convolutionImage1 = new Mat();
			Mat kernel = new Mat(3, 3, CvType.CV_32F);
			kernel.put(0, 0, -1);
			kernel.put(0, 1, -2);
			kernel.put(0, 2, -1);
			kernel.put(1, 0, 0);
			kernel.put(1, 1, 0);
			kernel.put(1, 2, 0);
			kernel.put(2, 0, 1);
			kernel.put(2, 1, 2);
			kernel.put(2, 2, 1);
			convolucion(image, kernel, convolutionImage1);

			Mat convolutionImage2 = new Mat();
			kernel.put(0, 0, -1);
			kernel.put(0, 1, 0);
			kernel.put(0, 2, 1);
			kernel.put(1, 0, -2);
			kernel.put(1, 1, 0);
			kernel.put(1, 2, 2);
			kernel.put(2, 0, -1);
			kernel.put(2, 1, 0);
			kernel.put(2, 2, 1);
			convolucion(image, kernel, convolutionImage2);

			Core.add(convolutionImage1, convolutionImage2, rectas);

			Mat diagonales = new Mat();
			Mat convolutionImage3 = new Mat();
			kernel.put(0, 0, -2);
			kernel.put(0, 1, -1);
			kernel.put(0, 2, 0);
			kernel.put(1, 0, -1);
			kernel.put(1, 1, 0);
			kernel.put(1, 2, 1);
			kernel.put(2, 0, 0);
			kernel.put(2, 1, 1);
			kernel.put(2, 2, 2);
			convolucion(image, kernel, convolutionImage3);

			Mat convolutionImage4 = new Mat();
			kernel.put(0, 0, 0);
			kernel.put(0, 1, -1);
			kernel.put(0, 2, -2);
			kernel.put(1, 0, 1);
			kernel.put(1, 1, 0);
			kernel.put(1, 2, -1);
			kernel.put(2, 0, 2);
			kernel.put(2, 1, 1);
			kernel.put(2, 2, 0);
			convolucion(image, kernel, convolutionImage4);

			Core.add(convolutionImage3, convolutionImage4, diagonales);

			Core.add(rectas, diagonales, imageMod);
		} catch (Exception e) {
			throw new Exception("Excepcion en bordes cartas");
		}
	}
	
	public static int cascade(Mat img, String xml, Mat imgMod) throws Exception {
		CascadeClassifier detector = new CascadeClassifier(xml);
		
		img.copyTo(imgMod);
		
		MatOfRect detections = new MatOfRect();
		detector.detectMultiScale(imgMod, detections);

		for (Rect rect : detections.toArray()) {
			Core.rectangle(imgMod, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0,
					255, 0));
		}
		
		return detections.toArray().length;
	}
	
	public static void showResult(Mat img) {
		Imgproc.resize(img, img, new Size(img.size().width, img.size().height));
		MatOfByte matOfByte = new MatOfByte();
		Highgui.imencode(".jpg", img, matOfByte);
		byte[] byteArray = matOfByte.toArray();
		BufferedImage bufImage = null;
		try {
			InputStream in = new ByteArrayInputStream(byteArray);
			bufImage = ImageIO.read(in);
			JFrame frame = new JFrame();
			frame.getContentPane().add(new JLabel(new ImageIcon(bufImage)));
			frame.pack();
			frame.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
